package inputstream;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Scanner;
import java.util.Vector;
import java.util.regex.Pattern;

import errors.ClassNotANumberException;
import errors.ClassPatternMismatchException;

/**la classe rappresenta un oggetto che e' in grado, partendo dal una lista di stringhe, di
 * leggere gli oggetti di tipo CLASS partendo dalle stringhe contenute nel file di testo.
 * Affinche' il tutto funzioni correttamente, e' essenziale che alcuni parametri siano
 * impostati in modo corretto e che, ovviamente, il file da cui si sta cercando di caricare gli
 * oggetti sia costituito da stringhe convertibili. I parametri da settare sono:
 * <ul>
 *  <li>la classe CLASS, rappresentante la classe su cui ci si basera' per convertire le stringhe lette
 *  <li>lo stringseprator, ovvero la stringa che divide le 2 stringhe che contengono le informazioni
 *  base per costruire una nuova istanza di CLASS</li>
 *  <li>l'informationpattern, ovvero il pattern con cui si costruira' l'istnaza CLASS partendo dalla scritta  
 * </ul>
 * senza dubbio la parte piu' importante e complessa per poter utilizzare la classe e' l'information pattern:
 * il pattern contiene come gli attributi devono essere scritti nella stringa per poter essere correttamente
 * letti dal ClassStream. quando si vuole inserire il valore dell'attributo bisogna scrivere
 * <strong>^NOME_ATTRIBUTO$</strong>: in questo modo si dice alla classe che in quel posto c'�
 * il valore dell'attributo scelto. Gli attributi non possono essere di qualunque tipo:
 * possono essere solo:
 * <ul>
 *  <li> tipi primitivi (short,int,float,double)</li>
 *  <li> classi rappresentanti tipi primitivi (Short,Integer,Float,Double)</li>
 *  <li> tipo String</li>
 * </ul>
 * <p>Per esempio, immaginiamo di avere un file con dentro varie istanze della classe Studente che ha i 3 attributi
 * studentID, studentName, studentPassword ognuno su una riga disposti come:
 * <p>[NOME = "Massimo"; MATRICOLA = "QWERTY"; PASSWORD $=$ "Java"]!
 * <p>in questo caso l'information pattern dovra' essere:
 * <p> [NOME = "^studentName$"; MATRICOLA = "^studentID$"; PASSWORD $=$ "^studentPassword$"]!.
 * invece lo stringseparator dovra' essere il semplice \n
 *
 * <p>Anche la classe CLASS ha dei limiti: affinche' la classe funzioni correttamente, <strong>la classe CLASS
 * deve possedere un costruttore con nessun parametro in ingresso</strong>: non importa se poi tale costruttore e' 
 * vuoto o no, l'importante e' che lo possegga!
 * 
 * @author Koldar
 * @version 1.3
 *
 */
public class ClassStream {
	
	public static final int NULL=0;
	
	private static final String START_FIELD="@S";
	private static final String END_FIELD="@E";
	private static final String JAVA_IDENTIFIER="[a-zA-Z_]([a-zA-Z_0-9\\Q$\\E])*";
	private static final String FIELD_DELIMITER="(^|"+END_FIELD+")"+"[^"+START_FIELD+"]*"+"($|"+START_FIELD+")";
	
	/**indica la sorgente da cui prendere le varie stringhe rappresentanti gli oggetti*/
	protected InputStream sourcestream;
	/**rappresenta lo scanner che traccia le stringhe rappresentanti un'istanza della classe*/
	protected Scanner scannerreader;
	/**rappresenta la stringa che divide 2 diverse istanze della classe CLASS*/
	protected String stringseparator;
	/**indica come sono distribuiti gli attributi in stringa per generare una nuova istanza della classe*/
	protected String informationpattern;
	/**indica il pattern in espresisone regolare dell'informationpattern*/
	public String regularexpressionpattern;
	protected int inputerror;
	protected Class<?> classtype;
	/**indica la lista SEQUENZIALE degli attributi che compaiono all'interno dell'information pattern*/ 
	protected Vector<String> fieldlist;
	/**indica la lista dei delimitatori che dividono l'inizio della stringa da convertire, i vari attributi e la sua fine*/
	protected String delimiterlist;

	
	/**costruisce un nuovo oggetto richiedente vari parametri. il tipo della classe pu� essere di
	 * quaunque tipo: l'importante e' che i suoi attributi siano tutti di tipo elementare (o di tipo stringa)
	 * 
	 * @param _classtype indica la classe tale che le stringhe lette siano convertite in tale classe 
	 * @param _sourcestream indica la sorgente da cui ricavare le stringhe da convertire
	 * @param _stringseparator indica la stringa che divide 2 stringhe da convertire
	 * @param _informationpattern indica il pattern cui ogni stringa da convertire deve soddisfare 
	 */
	public ClassStream(Class<?> _classtype,InputStream _sourcestream,String _stringseparator,String _informationpattern){
		this.classtype=_classtype;
		this.sourcestream=_sourcestream;
		this.stringseparator=_stringseparator;
		this.scannerreader=new Scanner(this.sourcestream).useDelimiter(Pattern.quote(this.stringseparator));
		this.informationpattern=_informationpattern;
		this.inputerror=NULL;
		this.fieldlist=new Vector<String>(0,1);
		
		this.regularexpressionpattern=this.informationpattern.replaceAll(
				Pattern.quote(START_FIELD)+JAVA_IDENTIFIER+Pattern.quote(END_FIELD),
				".+");
		Scanner fieldscanner=new Scanner(this.informationpattern).useDelimiter(FIELD_DELIMITER);
		this.delimiterlist=fieldscanner.findInLine(FIELD_DELIMITER);
		while (fieldscanner.hasNext()){
			this.fieldlist.add(fieldscanner.next());
			this.delimiterlist=this.delimiterlist+"|"+fieldscanner.findInLine(FIELD_DELIMITER);
		}
		this.delimiterlist=this.delimiterlist.replaceAll(Pattern.quote(START_FIELD),"").replaceAll(Pattern.quote(END_FIELD),"");
		
	}
	
	public Object readSecureClass(){
		//TODO fare un metodo che gestisca tutte le varie eccezioni!
		return null;
	}
	/** legge una singola stringa dal flusso dati scelto e la converte nell'oggetto ricercato
	 * 
	 * @return l'oggetto (sotto la classe Object) ricavato
	 * @throws ClassPatternMismatchException
	 * @throws InstantiationException in caso che la classe CLASS non possegga alcun costruttore CLASS()
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException in caso che la classe definita dal costruttore non possegga l'attributo passato nell'{@link #informationpattern}
	 * @throws SecurityException
	 * @throws ClassNotANumberException
	 * @throws ClassNotFoundException
	 */
	public Object readUnsecureClass() throws ClassPatternMismatchException, InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException, ClassNotANumberException, ClassNotFoundException{
		if (this.scannerreader.hasNext()){
			String temp=this.scannerreader.next();
			int i=0;
			
			if (!temp.matches(this.regularexpressionpattern)){
				throw new ClassPatternMismatchException(temp,this.regularexpressionpattern);
			}
			
			StringStream fieldscanner=new StringStream(new StringInputStream(temp),this.delimiterlist);
			Object instance=this.classtype.newInstance();
			Field field;
			while (fieldscanner.hasNext()){
				field=this.classtype.getField(this.fieldlist.get(i));
				if (field.getType()==String.class){
					field.set(instance,fieldscanner.readString());
				}
				if (field.getType().getSuperclass()==Number.class){
					field.set(instance, fieldscanner.readUnsafeNumber(field.getType()));
				}
				if (field.getType().isPrimitive()){
					String primitivename=field.getType().getSimpleName();
					field.set(instance, fieldscanner.readUnsafeNumber(
							ClassLoader.getSystemClassLoader().loadClass(
									"java.lang."+(primitivename=="int"?
											"Integer":
											Character.toUpperCase(primitivename.charAt(0))+primitivename.substring(1)))));
				}
				i++;
			}
			return instance;
		}else{
			return null;
		}
	}

}
